using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;

using System.IO;
using System.Threading;
using System.IO.Compression;

namespace RemoteObject
{
    [Serializable]
    public struct JobInfo
    {
        public float progress;
        public float downloadSpeed;
    }

    [Serializable]
    public class TransferParam
    {
        public int BlockSize = 1024 * 400;
        public int BuffSize  = 1024 * 400 * 5; // ignored
        public Boolean useCompressMode = false;
    }

    public class ClientInfo
    {
        public TransferParam param = new TransferParam();

        FileStream fs;
        Byte[] blockBuffer = null;

        long clientID;
        int startPos, remainReadDataLength, remainSendDataLength;

        bool clientDisposed = false;

        AutoResetEvent readNextBlockEvent;

        public void Init( String fn, int offset, int length, long _clientID )
        {
            clientID = _clientID;
            startPos = offset;
            remainReadDataLength = remainSendDataLength = length;

            fs = new FileStream(fn, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
            fs.Seek(startPos, SeekOrigin.Begin);

            readNextBlockEvent = new AutoResetEvent(true);
            Thread thread = new Thread(new ThreadStart(this.ReadFileBlockProc));
            thread.Start();
        }

        private void ReadFileBlockProc()
        {
            while (true)
            {
                //wait GetNextBlock's notification for next block reading.
                double nMinutes = 15;
                if (false == readNextBlockEvent.WaitOne((int)(1000 * 60 * nMinutes)))
                {
                    //no response for nMinutes, then kill this client
                    this.Release();
                    break;
                }

                if (clientDisposed)
                    break;

                //read ONE block data to buff
                lock (this)
                {
                    if (fs == null || remainReadDataLength <= 0)
                        break;

                    if (blockBuffer==null) // this has to be true
                    {
                        blockBuffer = new Byte[Math.Min(remainReadDataLength, param.BlockSize)];
                        int readLength = fs.Read(blockBuffer, 0, blockBuffer.Length);
                        remainReadDataLength -= readLength;
                        if (param.useCompressMode)
                        {
                            //compute the compress rate
                            Byte[] compressedBlock = MyUtility.Compression(blockBuffer, CompressionMode.Compress);
                            blockBuffer = compressedBlock;
                        }
                    }
                    if (remainReadDataLength <= 0)
                        break;
                }
            }
        }
        
        public void Release()
        {
            try
            {
                clientDisposed = true;
                MyRemoteObject.clientList.Remove(clientID);
                readNextBlockEvent.Set(); //ask WriteFileProc to quit
                lock (this)
                {
                    if (fs != null)
                    {
                        fs.Close();
                        fs = null;
                    }
                }
                System.GC.Collect();
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
        }

        public Byte[] GetNextBlock()
        {
            if (remainSendDataLength <= 0)
                return null;

            //RefillBuff();
            //Thread.Sleep(2000);
            
            //waiting for blockBuffer to be filled
            int waitTimes = 0;
            while (blockBuffer==null)
            {
                waitTimes++;
                Thread.Sleep(10);
                if (waitTimes > 100 * 60)
                {
                    //read file time out and close this client connection.
                    Release();
                    return null;
                }
            }

            Byte[] blockToSend = null;
            lock (this)
            {
                //take the blockBuffer and set it NULL
                blockToSend = blockBuffer;
                remainSendDataLength -= blockToSend.Length;

                //let ReadFile thread to fill the blockBuffer
                blockBuffer = null;
                readNextBlockEvent.Set();
            }

            return blockToSend;
        }

    }

    public class MyUtility
    {
        public static byte[] DeCompression(byte[] data)
        {
            return Compression(data, CompressionMode.Decompress);
        }

        public static byte[] Compression(byte[] data, CompressionMode mode)
        {
            DeflateStream zip = null;
            try
            {
                if (mode == CompressionMode.Compress)
                {
                    MemoryStream ms = new MemoryStream();
                    zip = new DeflateStream(ms, mode, true);
                    zip.Write(data, 0, data.Length);
                    zip.Close();
                    return ms.ToArray();
                }
                else
                {
                    MemoryStream ms = new MemoryStream();
                    ms.Write(data, 0, data.Length);
                    ms.Flush();
                    ms.Position = 0;
                    zip = new DeflateStream(ms, mode, true);
                    MemoryStream os = new MemoryStream();
                    int SIZE = 1024;
                    byte[] buf = new byte[SIZE];
                    int l = 0;
                    do
                    {
                        l = zip.Read(buf, 0, SIZE);
                        if (l == 0) l = zip.Read(buf, 0, SIZE);
                        os.Write(buf, 0, l);
                    }
                    while (l != 0);
                    zip.Close();
                    return os.ToArray();
                }
            }
            catch
            {
                if (zip != null) zip.Close();
                return null;
            }
            finally
            {
                if (zip != null) zip.Close();
            }
        }
    }

    public class MyRemoteObject : MarshalByRefObject
    {

        public delegate void MessageHandler(string fnRemote, string fnLocal, string ip, long clientID);
        public static event MessageHandler OnUploadRequest;
        public static event MessageHandler OnDistributeRequest;

        public delegate void MessageHandler2 (long clientID);
        public static event MessageHandler2 OnStopUploadRequest;

        public delegate JobInfo MessageHandler3(long clientID);
        public static event MessageHandler3 OnSyncRequest;

        

        public void StartUploadRequest(string remoteFile, string localFile, string ip, long jobID)
        {
            if (OnUploadRequest != null)
                OnUploadRequest(remoteFile, localFile, ip, jobID);
        }

        public void DistributeRequest(string remoteFile, string localFile, string ip, long jobID)
        {
            if (OnDistributeRequest != null)
                OnDistributeRequest(remoteFile, localFile, ip, jobID);
        }

        public void StopUploadRequest(long jobID)
        {
            if (OnStopUploadRequest != null)
                OnStopUploadRequest(jobID);
        }

        public JobInfo SyncRequest(long jobID)
        {
            if (OnSyncRequest != null)
                return OnSyncRequest(jobID);
            return new JobInfo();
        }

        public Byte[] GetDataTest()
        {
            return new Byte[1024 * 4];
        }

        //------------------------------------------------------------------------------------------------
        static long clientID = 0;
        public static Hashtable clientList = new Hashtable();

        public long GetNewClientID()
        {
            lock (clientList)
            {
                return ++clientID;
            }
        }

        public long GetFileLength(String fn)
        {
            if (!File.Exists(fn))
                return -1;

            FileInfo fileInfo = new FileInfo(fn);
            return fileInfo.Length;
        }


        public void SetTransferParam(long clientID, TransferParam param )
        {
            if (!clientList.Contains(clientID))
            {
                //create new Client and put it in gloal hashtable.
                ClientInfo newCI = new ClientInfo();
                newCI.param = param;
                clientList.Add(clientID, newCI);
            }
            else
            {
                ClientInfo ci = (ClientInfo)clientList[clientID];
                ci.param = param;
            }
        }

        public long StartDownloadFile(String fn, long clientID, int startPos, int dataLength)
        {
            if (!File.Exists(fn))
                return -1;

            if (clientList.Contains(clientID))
            {
                ClientInfo ci = (ClientInfo)clientList[clientID];
                ci.Init(fn, startPos, dataLength, clientID);
            }
            else
            {
                //create new Client and put it in gloal hashtable.
                ClientInfo newCI = new ClientInfo();
                newCI.Init(fn, startPos, dataLength, clientID);
                clientList.Add(clientID, newCI);
            }

            FileInfo fileInfo = new FileInfo(fn);
            return fileInfo.Length;
        }

        public int StopDownloadFile(long clientID)
        {
            if (!clientList.Contains(clientID))
                return -1;

            ClientInfo ci = (ClientInfo)clientList[clientID];
            ci.Release();
            
            return 0;
        }

        public Byte[] RecevingFileData(long clientID)
        {
            ClientInfo ci;
            Byte[] block = null;
            if (clientList.Contains(clientID))
            {
                ci = (ClientInfo)clientList[clientID];
                block = ci.GetNextBlock();
            }
            return block;
        }

        /* 
        static int max_calls = 0;
        static int cur_calls = 0;
        static Random rNumber = new Random();
        static public Byte[] RecevingFileDataTest( long jobID )
        {
            //Console.WriteLine(System.AppDomain.GetCurrentThreadId());
            lock (this)
            {
                cur_calls++;
                if (cur_calls > max_calls)
                {
                    max_calls = cur_calls;
                    Console.WriteLine("max_calls : " + max_calls);
                }
            }

            if( max_calls < 8 )
                Thread.Sleep(rNumber.Next(10, 99));
            
            ClientInfo ci;
            Byte[] block = null;
            if (clientList.Contains(jobID))
            {
                ci = (ClientInfo)clientList[jobID];
                block = ci.GetNextBlock();
            }
            lock (this)
            {
                cur_calls--;
            }
            return block;
        }
        */

        public Byte HeartBeatObserver()
        {
            return 1;
        }
    } 
}
